package org.vectrics.user;

import java.security.Principal;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import javax.servlet.http.HttpSession;

import org.apache.log4j.Logger;
import org.vectrics.SystemException;
import org.vectrics.domain.Domain;
import org.vectrics.hibernate.ThreadProperties;
import org.vectrics.provider.CoreServiceLocator;
import org.vectrics.security.ProtectedResource;
import org.vectrics.security.ProtectedResourceUserPrivilege;
import org.vectrics.security.UserPrivilegeToUserPrivilegeXRef;
import org.vectrics.struts.HttpRequestConverter;
import org.vectrics.struts.Request;
import org.vectrics.system.PagePrivilege;

abstract public class UserAuthService {
    static public String AUTH_PERSON_KEY = "auth_person";

    private static HashMap protectedResourceRoleMap = null;

    private Collection allPagePrivileges = null;

    private transient final Logger log = Logger.getLogger(this.getClass().getName());

    public UserAuthService() {
        super();
    }

    // User must be authenticated - role will be temporary unless other
    // persists it.
    public boolean addUserPrivilege(final Request request, final String userPrivilege, final Domain domain,
            final boolean global) {
        this.log.debug("addUserPrivilege() - adding role to user, role is not being persisted by this method.");
        boolean success = false;
        Person person = this.getAuthorizedPerson(domain, request);
        if (person != null) {
            person.addPrivilege(userPrivilege, domain, global);
            success = true;
        }
        return success;
    }

    public void clearProtectedResourceRoleMap() {
        this.getProtectedResourceRoleMap().clear();
    }

    /**
     * Does one user have the right to assign a particular privilege to another
     * user.
     * 
     * @param authPerson
     * @param userPrivilegeType
     * @param protResCode
     * @return
     */
    public boolean doesOneUserHaveRightToAssignRoleToOtherUser(final Person authPerson,
            final UserPrivilegeType userPrivilegeType, final String protResCode) {
        boolean result = false;
        ProtectedResource protectedResource = this.findProtectedResource(ProtectedResource.ASSIGN_PRIVILEGE);
        Collection allUserPrivilegeXrefs = this.findUserPrivilegeToUserPrivilegeXrefs(authPerson, userPrivilegeType,
                protectedResource);
        Iterator iter = allUserPrivilegeXrefs.iterator();
        while (result == false && iter.hasNext()) {
            UserPrivilegeToUserPrivilegeXRef xref = (UserPrivilegeToUserPrivilegeXRef) iter.next();
            if (xref.getProtectedResource().getCode().equals("assign.priv")) {
                result = true;
                this.log.info("xref did match: " + xref);
            } else {
                this.log.info("xref didn't match: " + xref);
            }
        }

        this.log.info("Checking for a user to have the rights to assign roles to other user"
                + "\r\n - result =            " + result + "\r\n - authPerson =        " + authPerson
                + "\r\n - userPrivilegeType = " + userPrivilegeType + "\r\n - xref count =        "
                + allUserPrivilegeXrefs.size() + "\r\n - protResCode =       " + protResCode);
        return result;
    }

    public String encodeCredentials(final String userName, final String password, final Domain domain) {
        StringBuffer result = new StringBuffer();
        String str = userName + "|" + password + "|" + domain.getId().toString();
        byte[] bytes = str.getBytes();
        for (byte c : bytes) {
            byte b = c;
            b += 10;
            result.append((char) b);
        }
        return result.toString();
    }

    public abstract Collection findAllPagePrivileges();

    abstract public ProtectedResource findProtectedResource(String code);

    public PagePrivilege findRelevantPagePrivilege(final Request request) {
        PagePrivilege relevantPagePriv = null;
        Iterator iter = this.getAllPagePrivileges().iterator();
        while (iter.hasNext()) {
            PagePrivilege pagePriv = (PagePrivilege) iter.next();
            if (request.getRequestURI().startsWith(pagePriv.getPage())) {
                if (relevantPagePriv == null || pagePriv.getPage().length() > relevantPagePriv.getPage().length()) {
                    relevantPagePriv = pagePriv;
                }
            }
        }
        return relevantPagePriv;
    }

    abstract public Collection findUserPrivilegeToUserPrivilegeXrefs(Person authPerson,
            UserPrivilegeType userPrivilegeType, ProtectedResource protectedResource);

    public Collection getAllPagePrivileges() {
        if (this.allPagePrivileges == null) {
            this.allPagePrivileges = this.findAllPagePrivileges();
        }
        return this.allPagePrivileges;
    }

    public Person getAuthorizedPerson(final Domain domain, final Request request) {
        Person person = null;

        if (request != null) {
            person = this.getAuthorizedPerson(request);

            if (person == null) {
                this.log.debug("getUser(ServletRequest) - could not find user attached to"
                        + " session, attempting to find user principal");
                Principal userPrincipal = request.getUserPrincipal();
                if (userPrincipal != null) {
                    String userName = userPrincipal.getName();

                    if (userName != null && userName.length() > 0) {
                        this.log.debug("getUser() - User principal found - looking up user");
                        try {
                            person = CoreServiceLocator.getPersonService().findPersonWithUserName(domain, userName);
                            if (person == null) {
                                this.log.error("Could not find user with userName = " + userName);
                            }
                            this.setAuthorizedPerson(person, request);
                        } catch (Exception ex) {
                            this.log.error("Exception finding user with user name: " + userName, ex);
                        }
                    } else {
                        this.log.debug("Could not find user from servlet context principal");
                    }
                } else {
                    this.log.debug("User principal not found");
                }
            } else {
                this.log.debug("User principal found as session attribute");
            }
        } else {
            this.log.debug("getUser() - request is null");
        }
        return person;
    }

    public Person getAuthorizedPerson(final HttpSession session) {
        Person person = null;
        if (session != null) {
            Map<String, Object> sessionAttributes = HttpRequestConverter.getSessionAttributes(session);
            person = this.getAuthorizedPerson(sessionAttributes);
        }
        return person;
    }

    public Person getAuthorizedPerson(final Map<String, Object> sessionAttributes) {
        this.log.debug("getAuthorizedUser(session) - method to find user attached to session");
        Person person = null;
        person = (Person) sessionAttributes.get(AUTH_PERSON_KEY);
        try {
            if (person != null) {
                this.log.debug("getAuthorizedUser(session) - found user attached to session");
                ThreadProperties.setSystemUser(true);
                person = CoreServiceLocator.getPersonService().findPersonById(person.getId());
            }
        } catch (Exception de) {
            this.log.error("getAuthorizedUser(session) - exception looking up person, id = " + person.getId(), de);
        } finally {
            ThreadProperties.setSystemUser(false);
        }
        return person;
    }

    public Person getAuthorizedPerson(final Request request) {
        Person person = (Person) request.getSessionAttribute(AUTH_PERSON_KEY);
        try {
            if (person != null) {
                ThreadProperties.setSystemUser(true);
                person = CoreServiceLocator.getPersonService().findPersonById(person.getId());
            }
        } catch (Exception de) {
            this.log.error("getAuthorizedUser(session) - exception looking up person, id = " + person.getId(), de);
        } finally {
            ThreadProperties.setSystemUser(false);
        }
        return person;
    }

    public Domain getDomainFromUri(final Request request) {
        String uri = request.getRequestURI();
        Domain domain = null;
        this.log.debug("URI = " + uri);
        int pos = uri.indexOf("/311");
        if (pos >= 0) {
            String doStr = uri.substring(pos + 4);
            int endPos = doStr.indexOf("/");
            if (endPos > 0) {
                doStr = doStr.substring(0, endPos);
            }
            domain = CoreServiceLocator.getSystemService().findDomain(new Long(doStr));
            if (domain == null) {
                throw new SystemException("Domain not found for URI domain: " + doStr);
            }
            ThreadProperties.setDomain(domain);
            this.setSessionDomain(domain, request);
            this.log.debug("Domain: " + doStr);
        }
        return domain;
    }

    public Domain getSessionDomain(final Request request) {
        return (Domain) request.getSessionAttribute("domain");
    }

    public boolean hasAccess(final String protectedResourceCode, final Person person) {
        boolean result = false;
        if (person.getUser() != null) {
            Iterator iter = person.getPrivileges().iterator();
            while (iter.hasNext() && result == false) {
                UserPrivilege userPrivilege = (UserPrivilege) iter.next();
                String key = this.makeKey(protectedResourceCode, userPrivilege.getPrivilegeType());
                ProtectedResourceUserPrivilege protResRole = (ProtectedResourceUserPrivilege) this
                        .getProtectedResourceRoleMap().get(key);
                if (protResRole != null) {
                    result = true;
                }
            }
        }
        return result;
    }

    public boolean hasRightToAssignGroup(final Person authPerson, final PersonPrivilegeGroupType groupType,
            final Domain domain) {
        boolean result = false;
        result = authPerson.hasDomainOrGlobalPrivilege(groupType.getAssignPrivilegeType(), domain);

        return result;
    }

    public boolean hasRightToAssignPrivilege(final Person authPerson, final UserPrivilegeType userPrivilegeType) {
        return this.doesOneUserHaveRightToAssignRoleToOtherUser(authPerson, userPrivilegeType,
                ProtectedResource.ASSIGN_PRIVILEGE);
    }

    public boolean isPasswordValid(final String password) {
        boolean result = true;
        if (password.length() < 5) {
            result = false;
        }
        if (password.indexOf(" ") >= 0) {
            result = false;
        }
        return result;
    }

    public boolean isUserNameValid(final String userName) {
        boolean result = true;
        if (userName.length() < 5) {
            result = false;
        }
        if (userName.indexOf(" ") >= 0) {
            result = false;
        }
        return result;
    }

    /*
     * Server should be of the form: "www.vectrics.com" or "localhost" Context
     * should be of the form: "/some-dir"
     */
    abstract public void loginUser(Request request, String server, String context, Person person);

    /**
     * Method to determine if a person can access data assigned to a particular
     * domain.
     * 
     * @param person
     *            The person in question.
     * @param domain
     *            The data owner in question.
     * @return True if person can access the domain's data.
     */
    public boolean personCanAccessDomain(final Person person, final Domain domain) {
        boolean result = false;
        if (person == null) {
            throw new SystemException("Person passed was null");
        }
        if (person.getId() == null) {
            throw new SystemException("Person should be saved before checking access rights");
        }
        if (domain == null) {
            throw new SystemException("Domain was passed as null to this method");
        }

        if (person.getDomain().getId().equals(domain.getId())) {
            result = true;
        } else if (person.hasDomainOrGlobalRole(UserPrivilegeType.GLOBAL_DATAOWNER_ACCESS, domain)) {
            result = true;
        }
        return result;
    }

    public void setAuthorizedPerson(final Person person, final Request request) {
        if (person != null) {
            this.log.debug("Setting auth user: " + person);

            if (person.getUser() == null) {
                request.removeSessionAttribute(AUTH_PERSON_KEY);
            } else {
                try {
                    request.setSessionAttribute(AUTH_PERSON_KEY, person);
                } catch (Exception de) {
                    this.log.error("DataException finding user roles in setAuthorizedUser()", de);
                }
            }
        } else {
            request.removeSessionAttribute(AUTH_PERSON_KEY);
        }
        ThreadProperties.setPerson(person);
        this.log.info("Setting session auth person to: " + person);
    }

    public void setSessionDomain(final Domain domain, final Request request) {
        this.log.debug("Setting session domain to: " + domain.toString());
        request.setSessionAttribute("domain", domain);
        ThreadProperties.setDomain(domain);
        this.log.info("Setting session domain to: " + domain);
    }

    protected void addProtectedResourceUserPrivilege(final ProtectedResourceUserPrivilege protRole) {
        UserPrivilegeType userPrivilegeType = protRole.getUserPrivilegeType();
        String protRoleCode = protRole.getProtectedResource().getCode();
        String key = this.makeKey(protRoleCode, userPrivilegeType);

        ProtectedResourceUserPrivilege protResRole = (ProtectedResourceUserPrivilege) protectedResourceRoleMap.get(key);
        if (protResRole != null) {
            throw new SystemException("ProtectedResourceUserPrivilege has duplicate entry in database, code = "
                    + protRole.getProtectedResource().getCode() + " Role: " + protRole.getUserPrivilegeType().getCode());
        }
        protectedResourceRoleMap.put(key, protRole);
    }

    abstract protected void loadProtectedResourceRoleMap(HashMap protectedResourceRoleMap);

    private HashMap getProtectedResourceRoleMap() {
        if (protectedResourceRoleMap == null) {
            protectedResourceRoleMap = new HashMap();
            this.loadProtectedResourceRoleMap(protectedResourceRoleMap);
        }
        return protectedResourceRoleMap;
    }

    private String makeKey(final String protectResourceCode, final UserPrivilegeType userPrivilegeType) {
        String key = protectResourceCode;
        key += "##";
        key += userPrivilegeType.getCode();
        return key;
    }

    public abstract boolean doesUserHavePagePrivilege(Request request, Person person, Domain domain);

    public abstract boolean doesUserHavePagePrivilege(String uri, Person person);
}
